#include "headers.h"

bool LexicalAnalyzer::Analyze(QFile* file, QList<Lexeme> *lexemes, MapWithPairKey *symbolTable, Error *error)
{
    SymbolTable=symbolTable;
    SymbolTable->clear();
    blockCount=0;
    Blocks.clear();
    Blocks.push(blockCount);
    if(file==NULL)
    {
        SetError(Error(0xff0, "Null pointer error"));
        return false;
    }
    this->lexemes=lexemes;
    this->error=error;

    ClearLexemes();
    ClearError();
    lexValue.clear();

    QPoint CurPos(1, 0);
    char ch;
    state=AS_EMPTY;
    while(file->getChar(&ch))
    {
        stepBack=false;
        if(!ReadChar(ch, &CurPos))
            return false;
        if(stepBack)
        {
            CurPos.setY(CurPos.y()-1);
            file->ungetChar(ch);
        }
    }
    if(!ReadChar('\0', &CurPos))
        return false;
    return true;
}

void LexicalAnalyzer::ClearLexemes()
{
    if(this->lexemes!=NULL)
        this->lexemes->clear();
}

void LexicalAnalyzer::ClearError()
{
    if(this->error!=NULL)
        this->error->Clear();
}

void LexicalAnalyzer::AppendLexeme(Lexeme lexeme)
{
    if(this->lexemes!=NULL)
        this->lexemes->append(lexeme);
}

void LexicalAnalyzer::SetError(Error error)
{
    if(this->error!=NULL)
        *(this->error)=error;
}

inline void LexicalAnalyzer::AcceptChar(char ch, AnalizerState state)
{
    this->lexValue.append(ch);
    this->state=state;
}

inline void LexicalAnalyzer::WriteLexeme(QString type, QPoint pos, bool stepBack=true)
{
    AppendLexeme(Lexeme(type, lexValue, pos));
    state=AS_EMPTY;
    lexValue.clear();
    this->stepBack=stepBack;
}

bool LexicalAnalyzer::IsKeyword(QString identifier)
{
    if(KEYWORDS.contains(identifier))
        return true;
    return false;
}

bool LexicalAnalyzer::ReadChar(char ch, QPoint* CurPos)
{
    CurPos->setY(CurPos->y()+1);
    CharType type = TranslateChar(ch);
    switch (state)
    {
        ////////////////////////////// AS_EMPTY //////////////////////////////
    case AS_EMPTY:
        LexPos=*CurPos;
        switch (type)
        {
        case CT_LETTER:
            AcceptChar(ch, AS_IDENTIFIER);
            return true;
        case CT_DIGIT:
            AcceptChar(ch, AS_NUMERIC);
            return true;
        case CT_SEPARATOR:
            AcceptChar(ch, AS_EMPTY);
            WriteLexeme(lexValue, LexPos, false);
            if(ch=='{')
            {
                blockCount++;
                Blocks.push(blockCount);
            }
            else if(ch=='}')
            {
                Blocks.pop();
            }
            return true;
        case CT_QUOTE:
            AcceptChar(ch, AS_STRING);
            return true;
        case CT_APOSTROPHE:
            AcceptChar(ch, AS_CHAR);
            return true;
         case CT_ARITHMETIC:
            switch (ch)
            {
            case '+':
                AcceptChar(ch, AS_PLUS);
                return true;
            case '-':
                AcceptChar(ch, AS_MINUS);
                return true;
            case '*':
                AcceptChar(ch, AS_ASTERISK);
                return true;
            case '/':
                AcceptChar(ch, AS_DEVIDE);
                return true;
            case '%':
                AcceptChar(ch, AS_MOD);
                return true;
            case '!':
                AcceptChar(ch, AS_EXCLAMATION);
                return true;
            case '&':
                AcceptChar(ch, AS_AMPERSAND);
                return true;
            case '|':
                AcceptChar(ch, AS_VERTICAL_BAR);
                return true;
            case '<':
                AcceptChar(ch, AS_LESS);
                return true;
            case '>':
                AcceptChar(ch, AS_MORE);
                return true;
            case '=':
                AcceptChar(ch, AS_EQUIL);
                return true;
            }
        case CT_SPACE:
            return true;
        case CT_CONTROL_CODE:
            if(ch=='\n')
            {
                CurPos->setX(CurPos->x()+1);
                CurPos->setY(0);
            }
            return true;
        default:
            if(ch=='\0')
                return true;
            else
            {
                SetError(Error(0xfff, QObject::tr("Unknown characker found \''%1\'").arg(ch), (QObject*)&CurPos));
                return false;
            }
        }
        ////////////////////////////// AS_EIDENTIFIER //////////////////////////////
    case AS_IDENTIFIER:
        switch (type)
        {
        case CT_LETTER:
        case CT_DIGIT:
            AcceptChar(ch, AS_IDENTIFIER);
            return true;
        default:
            if(IsKeyword(lexValue))
            {
                if((QStringList()<<"abstract"<<"const"<<"final"<<"native"<<"private"<<"protected"<<"public"<<"static"<<"synchronized"<<"transient"<<"volatile").contains(lexValue))
                    WriteLexeme("modifier", LexPos);
                else if((QStringList()<<"boolean"<<"byte"<<"char"<<"double"<<"float"<<"int"<<"long"<<"short").contains(lexValue))
                    WriteLexeme("primitive-type", LexPos);
                else if((QStringList()<<"true"<<"false").contains(lexValue))
                    WriteLexeme("bool-literal", LexPos);
                else if((QStringList()<<"break"<<"continue").contains(lexValue))
                    WriteLexeme("loop-control", LexPos);
                else if((QStringList()<<"super"<<"this").contains(lexValue))
                    WriteLexeme("class-ref", LexPos);
                else
                    WriteLexeme(lexValue, LexPos);
            }
            else
            {
                QPair<QString, int> key(lexValue, Blocks.top());
                SymbolTable->insert(key, Description());
                WriteLexeme("id", LexPos);
            }
            return true;
        }
        ////////////////////////////// AS_NUMERIC | AS_FLOAT //////////////////////////////
    case AS_NUMERIC:
    case AS_FLOAT:
        switch (type)
        {
        case CT_DIGIT:
            AcceptChar(ch, AS_NUMERIC);
            return true;
        case CT_LETTER:
            switch(ch)
            {
            case 'E':
            case 'e':
                AcceptChar(ch, AS_NUMERIC_EXP);
                return true;
            default:
                SetError(Error(0xffe, QObject::tr("Wrong suffix \'%1\' near numeric literal").arg(ch), (QObject*)&LexPos));
                return false;
            }
        case CT_SEPARATOR:
            if (ch=='.')
            {
                if(state==AS_NUMERIC)
                {
                    AcceptChar(ch, AS_NUMERIC_DOT);
                    return true;
                }
                else
                {
                    SetError(Error(0xffd, "Multiple dots in numeric literal", (QObject*)&LexPos));
                    return false;
                }
            }
        default:            
            WriteLexeme((state==AS_NUMERIC)?"int-literal":"float-literal", LexPos);
            return true;
        }
        ////////////////////////////// AS_NUMERIC_DOT //////////////////////////////
    case AS_NUMERIC_DOT:
        switch (type)
        {
        case CT_DIGIT:
            AcceptChar(ch, AS_FLOAT);
            return true;
        default:
            SetError(Error(0xffc, "No digits after dot in numeric literal", (QObject*)&LexPos));
            return false;
        }
        ////////////////////////////// AS_NUMERIC_EXP | AS_NUMERIC_EXP_SIGN //////////////////////////////
    case AS_NUMERIC_EXP:
    case AS_NUMERIC_EXP_SIGN:
        switch (type)
        {
        case CT_DIGIT:
            AcceptChar(ch, AS_NUMERIC_RANK);
            return true;
        case CT_ARITHMETIC:
            if (ch=='+' || ch=='-')
            {
                if(state==AS_NUMERIC_EXP)
                {
                    AcceptChar(ch, AS_NUMERIC_EXP_SIGN);
                    return true;
                }
            }
        default:
            SetError(Error(0xffb, "Exponent has no digits", (QObject*)&LexPos));
            return false;
        }
        ////////////////////////////// AS_NUMERIC_RANK //////////////////////////////
    case AS_NUMERIC_RANK:
        switch (type)
        {
        case CT_DIGIT:
            AcceptChar(ch, AS_NUMERIC_RANK);
            return true;
        case CT_LETTER:
            SetError(Error(0xffa, "Wrong suffix near exponent",(QObject*) &LexPos));
            return false;
        case CT_SEPARATOR:
            if(ch=='.')
            {
                SetError(Error(0xff9, "Unexpected dot in exponent", (QObject*)&LexPos));
                return false;
            }
        default:
            WriteLexeme("float-literal", LexPos);
            return true;
        }
        ////////////////////////////// AS_CHAR //////////////////////////////
    case AS_CHAR:
        switch(type)
        {
        case CT_CONTROL_CODE:
            SetError(Error(0xff8, "Unexpected control sequense", (QObject*)&LexPos));
            return false;
        default:
            if (ch=='\\')
            {
                AcceptChar(ch, AS_CHAR_BACKSLASH);
                return true;
            }
            if (ch=='\'')
            {
                SetError(Error(0xff7, "Empty character", (QObject*)&LexPos));
                return false;
            }
            AcceptChar(ch, AS_CHAR_OPEN);
            return true;
        }
        ////////////////////////////// AS_CHAR_BACKSLASH //////////////////////////////
    case AS_CHAR_BACKSLASH:
        switch(type)
        {
        case CT_CONTROL_CODE:
            SetError(Error(0xff8, "Unexpected control sequense", (QObject*)&LexPos));
            return false;
        default:
            AcceptChar(ch, AS_CHAR_OPEN);
            return true;
        }
        ////////////////////////////// AS_CHAR_OPEN //////////////////////////////
    case AS_CHAR_OPEN:
        if (ch=='\'')
        {
            AcceptChar(ch, AS_EMPTY);
            WriteLexeme("char-literal", LexPos, false);
            return true;
        }
        else
        {
            SetError(Error(0xff6, "Multy symbols in char literal", (QObject*)&LexPos));
            return false;
        }
        ////////////////////////////// AS_STRING //////////////////////////////
    case AS_STRING:
        if (ch=='\\')
        {
            AcceptChar(ch, AS_STRING_BACKSLASH);
            return true;
        }
        if (ch=='\"')
        {
            AcceptChar(ch, AS_EMPTY);
            WriteLexeme("string-literal", LexPos, false);
            return true;
        }
        if(ch=='\n')
        {
            SetError(Error(0xff5, "Unexpected end of line", (QObject*)&LexPos));
            return false;
        }
        if(ch=='\0')
        {
            SetError(Error(0xff4, "Unexpected end of file", (QObject*)&LexPos));
            return false;
        }
        AcceptChar(ch, AS_STRING);
        return true;
        ////////////////////////////// AS_STRING_BACKSLASH //////////////////////////////
    case AS_STRING_BACKSLASH:
        if(type==CT_CONTROL_CODE)
        {
            SetError(Error(0xff8, "Unexpected control sequense", (QObject*)&LexPos));
            return false;
        }
        AcceptChar(ch, AS_STRING);
        return true;
        ////////////////////////////// AS_PLUS //////////////////////////////
    case AS_PLUS:
        if(ch=='+')
        {
            AcceptChar(ch, AS_EMPTY);
            WriteLexeme(lexValue, LexPos, false);
            return true;
        }
        else if(ch=='=')
        {
            AcceptChar(ch, AS_EMPTY);
            WriteLexeme("assign", LexPos, false);
            return true;
        }
        WriteLexeme(lexValue, LexPos);
        return true;
        ////////////////////////////// AS_MINUS //////////////////////////////
    case AS_MINUS:
        if(ch=='-')
        {
            AcceptChar(ch, AS_EMPTY);
            WriteLexeme(lexValue, LexPos, false);
            return true;
        }
        else if(ch=='=')
        {
            AcceptChar(ch, AS_EMPTY);
            WriteLexeme("assign", LexPos, false);
            return true;
        }
        WriteLexeme(lexValue, LexPos);
        return true;
        ////////////////////////////// AS_DEVIDE //////////////////////////////
    case AS_DEVIDE:
        if(ch=='=')
        {
            AcceptChar(ch, AS_EMPTY);
            WriteLexeme("assign", LexPos, false);
            return true;
        }
        if(ch=='/')
        {
            AcceptChar(ch, AS_SINGLELINE_COMMENT);
            return true;
        }
        if(ch=='*')
        {
            AcceptChar(ch, AS_MULTYLINE_COMMENT);
            return true;
        }
        WriteLexeme(lexValue, LexPos);
        return true;
        ////////////////////////////// AS_ASTERISK | AS_MOD //////////////////////////////
    case AS_ASTERISK:
    case AS_MOD:
        if(ch=='=')
        {
            AcceptChar(ch, AS_EMPTY);
            WriteLexeme("assign", LexPos, false);
            return true;
        }
        WriteLexeme(lexValue, LexPos);
        return true;
        ////////////////////////////// AS_LESS //////////////////////////////
    case AS_LESS:
        if(ch=='<')
        {
            AcceptChar(ch, AS_EMPTY);
            WriteLexeme(lexValue, LexPos, false);
            return true;
        }
        else if(ch=='=')
        {
            AcceptChar(ch, AS_EMPTY);
            WriteLexeme("compare", LexPos, false);
            return true;
        }
        WriteLexeme("compare", LexPos);
        return true;
        ////////////////////////////// AS_MORE //////////////////////////////
    case AS_MORE:
        if(ch=='>')
        {
            AcceptChar(ch, AS_MORE_MORE);
            return true;
        }
        else if(ch=='=')
        {
            AcceptChar(ch, AS_EMPTY);
            WriteLexeme("compare", LexPos, false);
            return true;
        }
        WriteLexeme("compare", LexPos);
        return true;
        ////////////////////////////// AS_MORE_MORE //////////////////////////////
    case AS_MORE_MORE:
        if(ch=='>')
        {
            AcceptChar(ch, AS_EMPTY);
            WriteLexeme(lexValue, LexPos, false);
            return true;
        }
        WriteLexeme(lexValue, LexPos);
        return true;
        ////////////////////////////// AS_AMPERSAND //////////////////////////////
    case AS_AMPERSAND:
        if(ch=='&')
        {
            AcceptChar(ch, AS_EMPTY);
            WriteLexeme(lexValue, LexPos, false);
            return true;
        }
        WriteLexeme(lexValue, LexPos);
        return true;
        ////////////////////////////// AS_VERTICAL_BAR //////////////////////////////
    case AS_VERTICAL_BAR:
        if(ch=='|')
        {
            AcceptChar(ch, AS_EMPTY);
            WriteLexeme(lexValue, LexPos, false);
            return true;
        }
        WriteLexeme(lexValue, LexPos);
        return true;
        ////////////////////////////// AS_EXCLAMATION | AS_EQUIL //////////////////////////////
    case AS_EXCLAMATION:
    case AS_EQUIL:
        if(ch=='=')
        {
            AcceptChar(ch, AS_EMPTY);
            WriteLexeme("compare", LexPos, false);
            return true;
        }
        WriteLexeme(lexValue=="="?"assign":lexValue, LexPos);
        return true;
        ////////////////////////////// AS_SINGLELINE_COMMENT //////////////////////////////
    case AS_SINGLELINE_COMMENT:
        if(ch=='\n' || ch=='\0')
        {
            CurPos->setX(CurPos->x()+1);
            CurPos->setY(0);
            WriteLexeme("comment", LexPos, false);
            return true;
        }
        AcceptChar(ch, AS_SINGLELINE_COMMENT);
        return true;
        ////////////////////////////// AS_MULTYLINE_COMMENT //////////////////////////////
    case AS_MULTYLINE_COMMENT:
        if(ch=='\n')
        {
            CurPos->setX(CurPos->x()+1);
            CurPos->setY(0);
            AcceptChar(ch, AS_MULTYLINE_COMMENT);
            return true;
        }
        if(ch=='*')
        {
            AcceptChar(ch, AS_MULTYLINE_END);
            return true;
        }
        if(ch=='\0')
        {
            SetError(Error(0xff4, "Unexpected end of file", (QObject*)&LexPos));
            return false;
        }
        AcceptChar(ch, AS_MULTYLINE_COMMENT);
        return true;
        ////////////////////////////// AS_MULTYLINE_END //////////////////////////////
    case AS_MULTYLINE_END:
        if(ch=='\n')
        {
            CurPos->setX(CurPos->x()+1);
            CurPos->setY(0);
            AcceptChar(ch, AS_MULTYLINE_COMMENT);
            return true;
        }
        if(ch=='/')
        {
            AcceptChar(ch, AS_EMPTY);
            WriteLexeme("comment", LexPos, false);
            return true;
        }
        if(ch=='\0')
        {
            SetError(Error(0xff4, "Unexpected end of file", (QObject*)&LexPos));
            return false;
        }
        AcceptChar(ch, AS_MULTYLINE_COMMENT);
        return true;
    default:
        SetError(Error(0xff3, "Unknown analizer state", (QObject*)&LexPos));
        return false;
    }
    return true;
}

CharType LexicalAnalyzer::TranslateChar(char ch)
{
    if (ch=='_' || isalpha(ch))
    {
        return CT_LETTER;
    }
    if (isdigit(ch))
    {
        return CT_DIGIT;
    }
    if (ch=='\"')
    {
        return CT_QUOTE;
    }
    if (ch=='\'')
    {
        return CT_APOSTROPHE;
    }

    if (ch=='\\')
    {
        return CT_BACKSLASH;
    }
    if (ch=='{' || ch=='}' || ch=='(' || ch==')' || ch=='[' || ch==']' || ch==';' || ch==':' || ch=='.' || ch==',')
    {
        return CT_SEPARATOR;
    }
    if (ch=='+' || ch=='-' || ch=='*' || ch=='/' || ch=='%' || ch=='!' || ch=='~' || ch=='&' || ch=='|' || ch=='^' || ch=='<' || ch=='>' || ch=='=')
    {
        return CT_ARITHMETIC;
    }
    if (iscntrl(ch) && isspace(ch))
    {
        return CT_CONTROL_CODE;
    }
    if (isspace(ch))
    {
        return CT_SPACE;
    }
    return CT_UNKNOWN;
}
